/*
 * Copyright (c) Huawei Technologies Co.,Ltd. 2023. All rights reserved.
 */
package org.opengauss.intercept;

import org.opengauss.Logger;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Invocation;

import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Title: the DoRecord class.
 * <p>
 * Description:
 *
 * @author justbk
 * @version [Tools 0.0.1, 2023/7/24]
 * @since 2023/7/24
 */
public class DoRecord {
    public static class PairRecord {
        public long time;
        public long count;
        public int depth;
        public String name;
        public String sql = "";
        public PairRecord() {
        }
        public PairRecord(long time, long count, int depth, String name) {
            this.time = time;
            this.count = count;
            this.depth = depth;
            this.name = name;
        }
    
        @Override
        public String toString() {
            if (time > 5) {
                return String.format("[%s, %d, %4d, %4d]", sql,  depth, count, time);
            } else {
                return String.format("[%s, %d, %4d, %4d]", name,  depth, count, time);
            }
        }
        
        public int compareTo(PairRecord pairRecord) {
            return (int)(time - pairRecord.time);
        }
        
        public static PairRecord parse(Invocation invocation, long time, int depth) {
            PairRecord pr = new PairRecord(time, 0, depth, invocation.getMethod().getName() + invocation.getArgs().length);
            pr.sql = getSql(invocation);
            return pr;
        }
    
        public static String getSql(final Invocation invocation) {
            Object[] args = invocation.getArgs();
            if (args.length > 2 && args[0] instanceof MappedStatement) {
                return ((MappedStatement)(args[0])).getBoundSql(args[1]).getSql();
            }
            return "unknown";
        }
    }
    private static class SelfQueue {
        private int maxSize;
        private PairRecord maxRecord = new PairRecord(0, 0, INIT_DEPTH, "unknown");
        private PairRecord minRecord = new PairRecord(1000000, 0, INIT_DEPTH, "unknown");
        private PriorityQueue<PairRecord> maxArray;
        private PriorityQueue<PairRecord> minArray;
        private Stack<PairRecord> stackRecords = new Stack<>();
        private List<Stack<PairRecord>> allTimes = new LinkedList<>();
        public SelfQueue(int size) {
            this.maxSize = size;
            maxArray = new PriorityQueue<PairRecord>((o1, o2) -> o1.compareTo(o2)) {
                @Override
                public boolean add(final PairRecord o) {
                    boolean isAdd = super.add(o);
                    if (size() > maxSize) {
                        poll();
                    }
                    return isAdd;
                }
            };
            minArray = new PriorityQueue<PairRecord>((o1, o2) -> o2.compareTo(o1)) {
                @Override
                public boolean add(final PairRecord o) {
                    boolean isAdd = super.add(o);
                    if (size() > maxSize) {
                        poll();
                    }
                    return isAdd;
                }
            };
        }
    
        public int getSize() {
            return maxArray.size();
        }
        public void add(PairRecord pairRecord) {
            stackRecords.add(pairRecord);
            if (pairRecord.depth != INIT_DEPTH) {
                return;
            } else {
                allTimes.add(stackRecords);
                stackRecords = new Stack<>();
            }
            maxArray.add(pairRecord);
            minArray.add(pairRecord);
            if (maxRecord.time < pairRecord.time) {
                maxRecord = pairRecord;
            }
            if (minRecord.time > pairRecord.time) {
                minRecord = pairRecord;
            }
        }
    }
    private volatile long total;
    private volatile long count;
    public static final int MAX_ARRAY = 10;
    public static final int INVALID_DEPTH = 0;
    public static final int INIT_DEPTH = INVALID_DEPTH + 1;
    private volatile SelfQueue validArrayLen = new SelfQueue(MAX_ARRAY);
    private String recordName;
    private static volatile AtomicInteger depth = new AtomicInteger(INVALID_DEPTH);
    
    public DoRecord(String name) {
        this.recordName = name;
    }
    
    @Override
    public String toString() {
        return "DoRecord-" + recordName;
    }
    
    private synchronized void record(PairRecord pairRecord) {
        count ++;
        total += pairRecord.time;
        pairRecord.count = count;
        validArrayLen.add(pairRecord);
    }
    
    public synchronized <T> void getRecord(long[] results, T[] maxArray, T[] minArray) {
        results[0] = total;
        results[1] = count;
        Object[] recordMaxArray = validArrayLen.maxArray.toArray();
        Object[] recordMinArray = validArrayLen.minArray.toArray();
        for (int i = 0, compareMax = Math.min(MAX_ARRAY, validArrayLen.getSize()); i < compareMax; i++) {
            maxArray[i] = (T) recordMaxArray[i];
            minArray[i] = (T) recordMinArray[i];
        }
        Logger.info(String.format("size=%d, max=%s min=%s", validArrayLen.getSize(), validArrayLen.maxRecord,
                validArrayLen.minRecord));
    }
    
    public Object doRecord(final Invocation invocation) throws InvocationTargetException, IllegalAccessException {
        long cur = System.currentTimeMillis();
        depth.getAndIncrement();
        Object ret = invocation.proceed();
        long process_time = System.currentTimeMillis() - cur;
        record(PairRecord.parse(invocation, process_time, depth.get()));
        depth.getAndDecrement();
        return ret;
    }
}
